package com.virtualparadigm.compositor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;

import com.virtualparadigm.identity.Identifiable;
import com.virtualparadigm.identity.ResourceIdentifier;

public class OneToManyAggregate<ThisType extends Identifiable, ThatType extends Identifiable> implements Serializable
{
	private static final long serialVersionUID = 1L;
	
	// Why need "wrapped" Aggregate instance?
	//   - b/c aggregate is implemented via composition
	private ThisType instance;
	private Map<ResourceIdentifier, ManyToOneAggregate<ThatType, ThisType>> manyToOneAggregateMap;
//	private Map<String, ManyToOneAggregate<ThatType, ThisType>> manyToOneAggregateMap;
	public OneToManyAggregate(ThisType instance)
	{
		this.setInstance(instance);
		this.manyToOneAggregateMap = new HashMap<ResourceIdentifier, ManyToOneAggregate<ThatType, ThisType>>();
//		this.manyToOneAggregateMap = new HashMap<String, ManyToOneAggregate<ThatType, ThisType>>();
	}
	
	public ThisType getInstance()
	{
		return instance;
	}
	private void setInstance(ThisType instance)
	{
		this.instance = instance;
	}

	// package protected
	ManyToOneAggregate<ThatType, ThisType> findManyToOneAggregate(ResourceIdentifier resourceIdentifier)
	{
		ManyToOneAggregate<ThatType, ThisType> aggregate = null;
		if(resourceIdentifier != null)
		{
			aggregate = this.manyToOneAggregateMap.get(resourceIdentifier);
		}
		return aggregate;
	}
	
	public ThatType findAggregate(ResourceIdentifier resourceIdentifier)
	{
		ThatType that = null;
		ManyToOneAggregate<ThatType, ThisType> manyToOneAggregate = this.findManyToOneAggregate(resourceIdentifier);
		if(manyToOneAggregate != null)
		{
			that = manyToOneAggregate.getInstance();
		}
		return that;
	}
	
	// find aggregates
	// package protected
	Collection<ManyToOneAggregate<ThatType, ThisType>> findManyToOneAggregates()
	{
		return Collections.unmodifiableCollection(this.manyToOneAggregateMap.values());
	}
	
	public <ReturnType> Collection<ReturnType> findAggregates()
	{
		List<ReturnType> typedAggregateList = new ArrayList<ReturnType>();
		for(ManyToOneAggregate<ThatType, ThisType> aggregate : this.findManyToOneAggregates())
		{
			typedAggregateList.add((ReturnType)aggregate.getInstance());
		}
		return Collections.unmodifiableCollection(typedAggregateList);
	}
	
	// package protected
	boolean addManyToOneAggregate(ManyToOneAggregate<ThatType, ThisType> manyToOneAggregate)
	{
		boolean status = false;
		if(manyToOneAggregate != null && manyToOneAggregate.getInstance() != null && manyToOneAggregate.getInstance().getResourceIdentifier() != null)
		{
			this.manyToOneAggregateMap.put(manyToOneAggregate.getInstance().getResourceIdentifier(), manyToOneAggregate);
			manyToOneAggregate.setOneToManyAggregate(this);
			status = true;
		}
		return status;
	}
	public boolean addAggregate(ThatType aggregate)
	{
		return this.addManyToOneAggregate(new ManyToOneAggregate<ThatType, ThisType>(aggregate, this));
	}
	
	public boolean removeAggregate(ResourceIdentifier resourceIdentifier)
	{
		boolean status = false;
		if(resourceIdentifier != null)
		{
			ManyToOneAggregate<ThatType, ThisType> manyToOneAggregate = this.findManyToOneAggregate(resourceIdentifier);
			if(manyToOneAggregate != null && manyToOneAggregate.getInstance() != null && manyToOneAggregate.getInstance().getResourceIdentifier() != null)
			{
				this.manyToOneAggregateMap.remove(manyToOneAggregate.getInstance().getResourceIdentifier());
				if(manyToOneAggregate.getOneToManyAggregate() != null && manyToOneAggregate.getOneToManyAggregate().equals(this))
				{
					manyToOneAggregate.setOneToManyAggregate(null);
				}
			}
			status = true;
		}
		return status;
	}
	
	public boolean clearAggregates()
	{
		boolean status = true;
		Collection<ManyToOneAggregate<ThatType, ThisType>> aggregates = this.manyToOneAggregateMap.values();
		if(aggregates != null)
		{
			for(ManyToOneAggregate<ThatType, ThisType> aggregate : aggregates)
			{
				status &= this.removeAggregate(aggregate.getInstance().getResourceIdentifier());
			}
		}
		return status;
	}
	
	// ================================================
	// UTILITY METHODS
	// ================================================
	public String toString()
	{
//		return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
		return ReflectionToStringBuilder.toString(this, ToStringStyleBuilder.toStringStyle);
	}
	public int hashCode()
	{
		HashCodeBuilder builder = new HashCodeBuilder();
	    builder.append(this.getInstance().getResourceIdentifier());
	    return builder.toHashCode();		
	}
	public boolean equals(Object obj)
	{
		if (obj == null)
		{
			return false;
		}
		if (obj == this)
		{
			return true;
		}
		if (obj.getClass() != getClass())
		{
			return false;
		}
		OneToManyAggregate<ThisType, ThatType> that = (OneToManyAggregate<ThisType, ThatType>)obj;
		EqualsBuilder builder = new EqualsBuilder();
	    builder.append(this.getInstance().getResourceIdentifier(), that.getInstance().getResourceIdentifier());
		return builder.isEquals();
	}
	
}